package deploy

import (
	"errors"
	"fmt"
	"os"
	"strings"

	"gddgame.cc/galaxy/asteroid"
	"gddgame.cc/galaxy/binocle"
	. "gddgame.cc/galaxy/common/dsl/db"
	. "gddgame.cc/galaxy/common/dsl/web"
	"gddgame.cc/galaxy/core/config"
	"gddgame.cc/galaxy/server/composer"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/def"
)

const PluginName = "deploy"

var (
	RootProjectPath  = ""
	ProjectToolsPath = ""
)

func init() {

	RegisterPlugin(PluginProvider{
		Name: PluginName,
		//Depends: []string{notice.PluginName, report.PluginName},

		OnInit: func(builder composer.Builder, core binocle.CoreClient) error {
			RootProjectPath = config.DeployPath + "/"
			ProjectToolsPath = config.ProjectToolsPath

			for _, value := range os.Environ() {
				if strings.HasPrefix(value, "PATH") {
					if err := os.Setenv("PATH", ProjectToolsPath+":"+strings.TrimLeft(value, "PATH=")); err != nil {
						fmt.Println("setenv", err)
					}
					break
				}
			}
			core.OnStarted(func() {
				core.Watching("deploy/project", func(message asteroid.Message) {
					recordId := utils.ToInt(message.Data())
					context := core.Context()

					var record ProjectRecord
					if _, err := ProjectRecordModel.PK(recordId).One(&record); err != nil {
						message.Response(err)
						return
					}
					if err := deployProject(context, &record); err != nil {
						message.Response(err)
						return
					}
				})
				core.Watching("deploy/record", func(message asteroid.Message) {
					recordId := utils.ToInt(message.Data())
					context := core.Context()

					var record ProjectRecord
					if _, err := ProjectRecordModel.PK(recordId).One(&record); err != nil {
						message.Response(err)
						return
					}
					if err := recoveryProject(context, &record); err != nil {
						message.Response(err)
						return
					}
				})
			})

			AddTable(&Table{
				Model: DataCenterModel,
			})
			AddTable(&Table{
				Model: ProjectModel,
			})
			AddTable(&Table{
				Model: ProjectModuleModel,
			})
			AddTable(&Table{
				Model: ProjectEnvironmentModel,
			})
			AddTable(&Table{
				Model: ProjectConfigModel,
			})
			AddTable(&Table{
				Model: ProjectRecordModel,
			})
			AddTable(&Table{
				Model: ModuleEnvironmentModel,
			})
			return nil
		},
		BindRouter: BindRouter,
	})
}

func BindRouter(builder composer.Builder, core binocle.CoreClient, r *SwaggerRouter) {
	// 部署项目
	r.Post("/deploy/project", Swagger{
		Controller: func(context def.Context, environmentId int, moduleId int, nodes []string) error {
			var moduleEnvironment ModuleEnvironment
			var project Project
			var dc DataCenter
			var projectModule Module
			var projectEnvironment Environment
			var configs []Config
			var record ProjectRecord
			vars := map[string]string{}
			if _, err := ModuleEnvironmentModel.Query().Where("=", "module_id", moduleId).Where("=", "environment_id", environmentId).One(&moduleEnvironment); err != nil {
				return SystemException(err)
			}
			if _, err := ProjectModuleModel.PK(moduleId).One(&projectModule); err != nil {
				return SystemException(err)
			}
			if _, err := ProjectEnvironmentModel.PK(environmentId).One(&projectEnvironment); err != nil {
				return SystemException(err)
			}
			if _, err := ProjectModel.PK(projectModule.ProjectId).One(&project); err != nil {
				return SystemException(err)
			}
			if _, err := ProjectConfigModel.Query().Where("=", "module_id", moduleEnvironment.ModuleId).Find(&configs); err != nil {
				return SystemException(err)
			}
			if projectEnvironment.DataCenterId > 0 {
				if _, err := DataCenterModel.PK(projectEnvironment.DataCenterId).One(&dc); err != nil {
					return SystemException(err)
				}
				for index := range dc.Vars {
					v := dc.Vars[index]
					vars[v.Key] = v.Value
				}
			}
			for index := range project.Vars {
				v := project.Vars[index]
				vars[v.Key] = v.Value
			}
			for index := range projectEnvironment.Vars {
				v := projectEnvironment.Vars[index]
				vars[v.Key] = v.Value
			}
			for index := range projectModule.Vars {
				v := projectModule.Vars[index]
				vars[v.Key] = v.Value
			}
			for index := range moduleEnvironment.Vars {
				v := moduleEnvironment.Vars[index]
				vars[v.Key] = v.Value
			}
			if project.Status != 1 {
				return ValidException("项目状态错误")
			}
			flow := projectModule.Flows
			if moduleEnvironment.Flows != nil {
				if err := flow.Merge(moduleEnvironment.Flows); err != nil {
					return ValidException(err)
				}
			}
			record = ProjectRecord{
				ProjectId:     project.Id,
				ModuleId:      projectModule.Id,
				EnvironmentId: projectEnvironment.Id,
				DataCenterId:  dc.Id,
				Identity:      projectModule.Identity,
				Type:          projectModule.Type,
				Nodes:         nodes,
				Configs:       configs,
				Vars:          vars,
				Process:       Wait,
				Flows:         flow,
			}
			if _, err := ProjectRecordModel.Query().Save(true, &record); err != nil {
				return SystemException(err)
			}
			if _, err := ProjectModel.PK(project.Id).Update(map[string]interface{}{
				"status": 2,
			}); err != nil {
				return SystemException(err)
			}
			if record.DataCenterId > 0 {
				// 转发到对应dc进行执行
				core.PublishPipe().Cross(map[string]string{dc.Identity: core.Name()}).Data(record.Id).Send("deploy/project")
			} else {
				// 本地执行
				if err := deployProject(context, &record); err != nil {
					return ValidException(err)
				}
			}
			return nil
		},
		Params: []Param{
			{Name: "environment_id", In: Body, Type: Int},
			{Name: "module_id", In: Body, Type: Int},
			{Name: "nodes", In: Body, Array: true},
		},
		Response: ApiResponse,
	})
	r.Get("/deploy/vars", Swagger{
		Controller: func(context def.Context, moduleEnvironmentId int) ([]VarItem, error) {
			var moduleEnvironment ModuleEnvironment
			var project Project
			var dc DataCenter
			var projectModule Module
			var projectEnvironment Environment
			vars := map[string]string{}
			if _, err := ModuleEnvironmentModel.PK(moduleEnvironmentId).One(&moduleEnvironment); err != nil {
				return nil, SystemException(err)
			}
			if _, err := ProjectModuleModel.PK(moduleEnvironment.ModuleId).One(&projectModule); err != nil {
				return nil, SystemException(err)
			}
			if _, err := ProjectEnvironmentModel.PK(moduleEnvironment.EnvironmentId).One(&projectEnvironment); err != nil {
				return nil, SystemException(err)
			}
			if _, err := ProjectModel.PK(projectModule.ProjectId).One(&project); err != nil {
				return nil, SystemException(err)
			}
			if projectEnvironment.DataCenterId > 0 {
				if _, err := DataCenterModel.PK(projectEnvironment.DataCenterId).One(&dc); err != nil {
					return nil, SystemException(err)
				}
				for index := range dc.Vars {
					v := dc.Vars[index]
					vars[v.Key] = v.Value
				}
			}
			for index := range project.Vars {
				v := project.Vars[index]
				vars[v.Key] = v.Value
			}
			for index := range projectEnvironment.Vars {
				v := projectEnvironment.Vars[index]
				vars[v.Key] = v.Value
			}
			for index := range projectModule.Vars {
				v := projectModule.Vars[index]
				vars[v.Key] = v.Value
			}
			for index := range moduleEnvironment.Vars {
				v := moduleEnvironment.Vars[index]
				vars[v.Key] = v.Value
			}
			varList := make([]VarItem, 0, len(vars))
			for key, value := range vars {
				varList = append(varList, VarItem{
					Key:   key,
					Value: value,
				})
			}
			return varList, nil
		},
		Params: []Param{
			{Name: "module_environment_id", In: Query, Type: Int},
		},
		Response: ApiResponse,
	})
	r.Post("/deploy/record", Swagger{
		Controller: func(context def.Context, recordId int) error {
			var record ProjectRecord
			var dc DataCenter
			if _, err := ProjectRecordModel.PK(recordId).One(&record); err != nil {
				return err
			}
			if record.DataCenterId > 0 {
				if _, err := DataCenterModel.PK(record.DataCenterId).One(&dc); err != nil {
					return SystemException(err)
				}
				// 转发到对应dc进行执行
				core.PublishPipe().Cross(map[string]string{dc.Identity: core.Name()}).Data(record.Id).Send("deploy/record")
			} else {
				// 本地执行
				if err := recoveryProject(context, &record); err != nil {
					return ValidException(err)
				}
			}
			return nil
		},
		Params: []Param{
			{Name: "record_id", In: Body, Type: Int},
		},
		Response: ApiResponse,
	})
}

func deployProject(context def.Context, record *ProjectRecord) error {
	var project Project

	result := ""
	defer recoverProject(record.ProjectId)

	if _, err := ProjectModel.PK(record.ProjectId).One(&project); err != nil {
		fmt.Println(err)
		return err
	}
	go func() {
		projectPath := RootProjectPath + record.Identity
		wrap := &Wrap{record: record, context: context, path: projectPath + "/"}
		record.Vars[ProjectIdentity] = record.Identity
		record.Vars[ProjectPath] = projectPath

		wrap.Process(InitProject, result, true)

		dcFile := projectPath + "/dc.json"
		_ = generateDcFile(dcFile)

		flag := -1
		for index, flow := range record.Flows {
			if flow.Params == nil {
				wrap.Process(InitProject, "pass flow:"+string(flow.Type)+"\n", true)
				continue
			}
			err := FlowDefine[flow.Type].Run(wrap, flow.Params)
			record.Flows[index] = flow
			if err != nil {
				wrap.Error(err, false)
				flag = index
				break
			}
		}
		if flag == -1 {
			wrap.Finish()
		} else {
			for i := flag; i >= 0; i-- {
				flow := record.Flows[i]
				if flow.Params == nil {
					continue
				}
				err := FlowDefine[flow.Type].Recovery(wrap, flow.Params)
				if err != nil {
					wrap.Error(err, false)
					break
				}
			}
			wrap.Error(errors.New("recovery finish"), true)
		}
	}()

	return nil
}
func recoveryProject(context def.Context, record *ProjectRecord) error {
	var project Project

	if _, err := ProjectModel.PK(record.ProjectId).One(&project); err != nil {
		fmt.Println(err)
		return err
	}
	go func() {
		projectPath := RootProjectPath + record.Identity
		wrap := &Wrap{record: record, context: context, path: projectPath + "/"}
		record.Vars[ProjectIdentity] = record.Identity
		record.Vars[ProjectPath] = projectPath

		for i := len(record.Flows) - 1; i >= 0; i-- {
			flow := record.Flows[i]
			err := FlowDefine[flow.Type].Recovery(wrap, flow.Params)
			if err != nil {
				wrap.Error(err, false)
				break
			}
		}
	}()

	return nil
}
